package com.dcy.system.filter.web.filter;

import com.dcy.system.filter.wrapper.RequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

/**
 * @author YuChangjiang
 */
@Slf4j
public abstract class BaseLogFilter extends OncePerRequestFilter {

    private static final List<String> EXCLUDED_PATH = new LinkedList<>();

    static {
        EXCLUDED_PATH.add("/static/*");
        EXCLUDED_PATH.add("/yxhis/*");
        EXCLUDED_PATH.add("/admin/*");
        EXCLUDED_PATH.add("/log/*");
        EXCLUDED_PATH.add("/logs/*");
        EXCLUDED_PATH.add("/deploy/*");
        EXCLUDED_PATH.add("/user/*");
        EXCLUDED_PATH.add("/noAuthor/*");
        EXCLUDED_PATH.add("/druid/*");
        EXCLUDED_PATH.add("/v3/api-docs");
        EXCLUDED_PATH.add("/v2/api-docs");
        EXCLUDED_PATH.add("/swagger-resources");
        EXCLUDED_PATH.add("/swagger-resources/*");
        EXCLUDED_PATH.add("*.html");
        EXCLUDED_PATH.add("*.js");
        EXCLUDED_PATH.add("*.ico");
        EXCLUDED_PATH.add("*.jpg");
        EXCLUDED_PATH.add("*.png");
        EXCLUDED_PATH.add("*.css");
        EXCLUDED_PATH.add("*.map");
        EXCLUDED_PATH.add("*.pdf");
    }

    /**
     * 添加需要排除的路径
     *
     * @param paths 需要排除的路径
     */
    public static void addExcludedPath(String... paths) {
        Collections.addAll(EXCLUDED_PATH, paths);
    }

    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        boolean isFirstRequest = !isAsyncDispatch(request);
        boolean isFilterExcludeRequest = !isFilterExcludeRequest(request);

        if (isFirstRequest && isFilterExcludeRequest) {
            HttpServletRequest requestToUse = request;
            HttpServletResponse responseToUse = response;

            if (!(request instanceof RequestWrapper)) {
                requestToUse = new RequestWrapper(request);
            }
            if (!(response instanceof ContentCachingResponseWrapper)) {
                responseToUse = new ContentCachingResponseWrapper(response);
            }

            try {
                beforeRequest(requestToUse, responseToUse);
                filterChain.doFilter(requestToUse, responseToUse);
            } finally {
                if (!isAsyncStarted(requestToUse)) {
                    afterRequest(requestToUse, responseToUse);
                    ContentCachingResponseWrapper wrappedResponse = WebUtils.getNativeResponse(responseToUse, ContentCachingResponseWrapper.class);
                    if (Objects.nonNull(wrappedResponse)) {
                        wrappedResponse.copyBodyToResponse();
                    }
                }
            }
        } else {
            filterChain.doFilter(request, response);
        }
    }

    protected abstract void beforeRequest(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException;

    protected abstract void afterRequest(HttpServletRequest request, HttpServletResponse response) throws IOException;

    /**
     * 判断是否是过滤器直接放行的请求(主要用于静态资源的放行)
     *
     * @param request http请求
     * @return boolean
     */
    private boolean isFilterExcludeRequest(HttpServletRequest request) {
        // 清除 URL 中重复斜杠（/）
        String url = this.cleanUrl(request.getRequestURI());
        String contextPath = request.getContextPath();

        for (String excludedUrl : EXCLUDED_PATH) {
            if (isUrlMatch(url, contextPath, excludedUrl)) {
                return true;
            }
        }

        return false;
    }

    private String cleanUrl(String url) {
        StringBuilder cleanedUrl = new StringBuilder(url.length());

        boolean prevCharWasSlash = false;
        for (char c : url.toCharArray()) {
            if (c == '/') {
                if (!prevCharWasSlash) {
                    cleanedUrl.append(c);
                    prevCharWasSlash = true;
                }
            } else {
                cleanedUrl.append(c);
                prevCharWasSlash = false;
            }
        }

        return cleanedUrl.toString();
    }

    private boolean isUrlMatch(String url, String contextPath, String excludedUrl) {
        if (excludedUrl.startsWith("*.")) {
            String suffix = excludedUrl.substring(2);
            // 如果配置的是后缀匹配, 则把前面的*号干掉，然后用endWith来判断
            return url.endsWith(suffix);
        } else if (excludedUrl.endsWith("/*")) {
            if (!excludedUrl.startsWith("/")) {
                // 前缀匹配，必须要是/开头
                excludedUrl = "/" + excludedUrl;
            }
            // 如果配置是前缀匹配, 则把最后的*号干掉，然后startWith来判断
            final String prefixStr = contextPath + excludedUrl.substring(0, excludedUrl.length() - 1);
            return url.startsWith(prefixStr);
        } else {
            // 如果不是前缀匹配也不是后缀匹配,那就是全路径匹配
            if (!excludedUrl.startsWith("/")) {
                // 全路径匹配，也必须要是/开头
                excludedUrl = "/" + excludedUrl;
            }
            final String targetUrl = contextPath + excludedUrl;
            return url.equals(targetUrl);
        }
    }
}
